home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-debian / debian_bundle / changelog.py < prev    next >
Encoding:
Python Source  |  2008-10-30  |  27.5 KB  |  705 lines

  1. # changelog.py -- Python module for Debian changelogs
  2. # Copyright (C) 2006-7 James Westby <jw+debian@jameswestby.net>
  3. # Copyright (C) 2008 Canonical Ltd.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18.  
  19. # The parsing code is based on that from dpkg which is:
  20. # Copyright 1996 Ian Jackson
  21. # Copyright 2005 Frank Lichtenheld <frank@lichtenheld.de>
  22. # and licensed under the same license as above.
  23.  
  24. """This module implements facilities to deal with Debian changelogs."""
  25.  
  26. import re
  27. import unittest
  28. import warnings
  29.  
  30. import debian_support
  31.  
  32. class ChangelogParseError(StandardError):
  33.     """Indicates that the changelog could not be parsed"""
  34.     is_user_error = True
  35.  
  36.     def __init__(self, line):
  37.         self._line=line
  38.  
  39.     def __str__(self):
  40.         return "Could not parse changelog: "+self._line
  41.  
  42. class ChangelogCreateError(StandardError):
  43.     """Indicates that changelog could not be created, as all the information
  44.     required was not given"""
  45.  
  46. class VersionError(StandardError):
  47.     """Indicates that the version does not conform to the required format"""
  48.  
  49.     is_user_error = True
  50.  
  51.     def __init__(self, version):
  52.         self._version=version
  53.  
  54.     def __str__(self):
  55.         return "Could not parse version: "+self._version
  56.  
  57. class Version(debian_support.Version, object):
  58.     """Represents a version of a Debian package."""
  59.     # Subclassing debian_support.Version for its rich comparison
  60.  
  61.     def __init__(self, version):
  62.         version = str(version)
  63.         debian_support.Version.__init__(self, version)
  64.  
  65.         self.full_version = version
  66.  
  67.     def __setattr__(self, attr, value):
  68.       """Update all the attributes, given a particular piece of the version
  69.   
  70.       Allowable values for attr, hopefully self-explanatory:
  71.         full_version
  72.         epoch
  73.         upstream_version
  74.         debian_version
  75.  
  76.       Any attribute starting with __ is given to object's __setattr__ method.
  77.       """
  78.  
  79.       attrs = ('full_version', 'epoch', 'upstream_version', 'debian_version')
  80.  
  81.       if attr.startswith('_Version__'):
  82.           object.__setattr__(self, attr, value)
  83.           return
  84.       elif attr not in attrs:
  85.           raise AttributeError("Cannot assign to attribute " + attr)
  86.  
  87.       if attr == 'full_version':
  88.           version = value
  89.           p = re.compile(r'^(?:(?P<epoch>\d+):)?'
  90.                          + r'(?P<upstream_version>[A-Za-z0-9.+:~-]+?)'
  91.                          + r'(?:-(?P<debian_version>[A-Za-z0-9.~+]+))?$')
  92.           m = p.match(version)
  93.           if m is None:
  94.               raise VersionError(version)
  95.           for key, value in m.groupdict().items():
  96.               object.__setattr__(self, key, value)
  97.           self.__asString = version
  98.     
  99.       else:
  100.           # Construct a full version from what was given and pass it back here
  101.           d = {}
  102.           for a in attrs[1:]:
  103.               if a == attr:
  104.                   d[a] = value
  105.               else:
  106.                   d[a] = getattr(self, a)
  107.  
  108.           version = ""
  109.           if d['epoch'] and d['epoch'] != '0':
  110.               version += d['epoch'] + ":"
  111.           version += d['upstream_version']
  112.           if d['debian_version']:
  113.               version += '-' + d['debian_version']
  114.  
  115.           self.full_version = version
  116.  
  117.     full_version = property(lambda self: self.__asString)
  118.  
  119. class ChangeBlock(object):
  120.     """Holds all the information about one block from the changelog."""
  121.  
  122.     def __init__(self, package=None, version=None, distributions=None,
  123.                 urgency=None, urgency_comment=None, changes=None,
  124.                 author=None, date=None, other_pairs=None):
  125.         self._raw_version = None
  126.         self._set_version(version)
  127.         self.package = package
  128.         self.distributions = distributions
  129.         self.urgency = urgency or "unknown"
  130.         self.urgency_comment = urgency_comment or ''
  131.         self._changes = changes
  132.         self.author = author
  133.         self.date = date
  134.         self._trailing = []
  135.         self.other_pairs = other_pairs or {}
  136.         self._no_trailer = False
  137.         self._trailer_separator = "  "
  138.  
  139.     def _set_version(self, version):
  140.         if version is not None:
  141.             self._raw_version = str(version)
  142.  
  143.     def _get_version(self):
  144.         return Version(self._raw_version)
  145.  
  146.     version = property(_get_version, _set_version)
  147.  
  148.     def other_keys_normalised(self):
  149.         norm_dict = {}
  150.         for (key, value) in other_pairs.items():
  151.             key = key[0].upper() + key[1:].lower()
  152.             m = xbcs_re.match(key)
  153.             if m is None:
  154.                 key = "XS-%s" % key
  155.             norm_dict[key] = value
  156.         return norm_dict
  157.  
  158.     def changes(self):
  159.         return self._changes
  160.  
  161.     def add_trailing_line(self, line):
  162.         self._trailing.append(line)
  163.  
  164.     def add_change(self, change):
  165.         if self._changes is None:
  166.             self._changes = [change]
  167.         else:
  168.             #Bit of trickery to keep the formatting nicer with a blank
  169.             #line at the end if there is one
  170.             changes = self._changes
  171.             changes.reverse()
  172.             added = False
  173.             for i in range(len(changes)):
  174.                 m = blankline.match(changes[i])
  175.                 if m is None:
  176.                     changes.insert(i, change)
  177.                     added = True
  178.                     break
  179.             changes.reverse()
  180.             if not added:
  181.                 changes.append(change)
  182.             self._changes = changes
  183.  
  184.     def __str__(self):
  185.         block = ""
  186.         if self.package is None:
  187.             raise ChangelogCreateError("Package not specified")
  188.         block += self.package + " "
  189.         if self._raw_version is None:
  190.             raise ChangelogCreateError("Version not specified")
  191.         block += "(" + self._raw_version + ") "
  192.         if self.distributions is None:
  193.             raise ChangelogCreateError("Distribution not specified")
  194.         block += self.distributions + "; "
  195.         if self.urgency is None:
  196.             raise ChangelogCreateError("Urgency not specified")
  197.         block += "urgency=" + self.urgency + self.urgency_comment
  198.         for (key, value) in self.other_pairs.items():
  199.             block += ", %s=%s" % (key, value)
  200.         block += '\n'
  201.         if self.changes() is None:
  202.             raise ChangelogCreateError("Changes not specified")
  203.         for change in self.changes():
  204.             block += change + "\n"
  205.         if not self._no_trailer:
  206.             if self.author is None:
  207.                 raise ChangelogCreateError("Author not specified")
  208.             if self.date is None:
  209.                 raise ChangelogCreateError("Date not specified")
  210.             block += " -- " + self.author + self._trailer_separator \
  211.                       + self.date + "\n"
  212.         for line in self._trailing:
  213.             block += line + "\n"
  214.         return block
  215.  
  216. topline = re.compile(r'^(\w%(name_chars)s*) \(([^\(\) \t]+)\)'
  217.                      '((\s+%(name_chars)s+)+)\;'
  218.                      % {'name_chars': '[-+0-9a-z.]'},
  219.                      re.IGNORECASE)
  220. blankline = re.compile('^\s*$')
  221. change = re.compile('^\s\s+.*$')
  222. endline = re.compile('^ -- (.*) <(.*)>(  ?)((\w+\,\s*)?\d{1,2}\s+\w+\s+'
  223.             '\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?\s*)$')
  224. endline_nodetails = re.compile('^ --(?: (.*) <(.*)>(  ?)((\w+\,\s*)?\d{1,2}'
  225.                 '\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}'
  226.                 '(\s+\([^\\\(\)]\))?))?\s*$')
  227. keyvalue= re.compile('^([-0-9a-z]+)=\s*(.*\S)$', re.IGNORECASE)
  228. value_re = re.compile('^([-0-9a-z]+)((\s+.*)?)$', re.IGNORECASE)
  229. xbcs_re = re.compile('^X[BCS]+-', re.IGNORECASE)
  230. emacs_variables = re.compile('^(;;\s*)?Local variables:', re.IGNORECASE)
  231. vim_variables = re.compile('^vim:', re.IGNORECASE)
  232. cvs_keyword = re.compile('^\$\w+:.*\$')
  233. comments = re.compile('^\# ')
  234. more_comments = re.compile('^/\*.*\*/')
  235.  
  236. old_format_re1 = re.compile('^(\w+\s+\w+\s+\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}'
  237.         '\s+[\w\s]*\d{4})\s+(.*)\s+(<|\()(.*)(\)|>)')
  238. old_format_re2 = re.compile('^(\w+\s+\w+\s+\d{1,2},?\s*\d{4})\s+(.*)'
  239.         '\s+(<|\()(.*)(\)|>)')
  240. old_format_re3 = re.compile('^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)\;?',
  241.         re.IGNORECASE)
  242. old_format_re4 = re.compile('^([\w.+-]+)(-| )(\S+) Debian (\S+)',
  243.         re.IGNORECASE)
  244. old_format_re5 = re.compile('^Changes from version (.*) to (.*):',
  245.         re.IGNORECASE)
  246. old_format_re6 = re.compile('^Changes for [\w.+-]+-[\w.+-]+:?\s*$',
  247.         re.IGNORECASE)
  248. old_format_re7 = re.compile('^Old Changelog:\s*$', re.IGNORECASE)
  249. old_format_re8 = re.compile('^(?:\d+:)?\w[\w.+~-]*:?\s*$')
  250.  
  251.  
  252. class Changelog(object):
  253.     """Represents a debian/changelog file. You can ask it several things
  254.        about the file.
  255.     """
  256.  
  257.  
  258.     def __init__(self, file=None, max_blocks=None,
  259.             allow_empty_author=False, strict=True):
  260.         """Set up the Changelog for use. file is the contects of the
  261.            changelog.
  262.         """
  263.         self._blocks = []
  264.         self.initial_blank_lines = []
  265.         if file is not None:
  266.             try:
  267.                 self.parse_changelog(file, max_blocks=max_blocks,
  268.                         allow_empty_author=allow_empty_author,
  269.                         strict=strict)
  270.             except ChangelogParseError:
  271.                 pass
  272.  
  273.     def _parse_error(self, message, strict):
  274.         if strict:
  275.             raise ChangelogParseError(message)
  276.         else:
  277.             warnings.warn(message)
  278.  
  279.     def parse_changelog(self, file, max_blocks=None,
  280.             allow_empty_author=False, strict=True):
  281.         first_heading = "first heading"
  282.         next_heading_or_eof = "next heading of EOF"
  283.         start_of_change_data = "start of change data"
  284.         more_changes_or_trailer = "more change data or trailer"
  285.         slurp_to_end = "slurp to end"
  286.  
  287.         self._blocks = []
  288.         self.initial_blank_lines = []
  289.  
  290.         current_block = ChangeBlock()
  291.         changes = []
  292.         
  293.         state = first_heading
  294.         old_state = None
  295.         if isinstance(file, basestring):
  296.             if file[-1] != '\n':
  297.                 file += '\n'
  298.             file = file.split('\n')[:-1]
  299.         for line in file:
  300.             if state == first_heading or state == next_heading_or_eof:
  301.                 top_match = topline.match(line)
  302.                 blank_match = blankline.match(line)
  303.                 if top_match is not None:
  304.                     if (max_blocks is not None
  305.                             and len(self._blocks) >= max_blocks):
  306.                         return
  307.                     current_block.package = top_match.group(1)
  308.                     current_block._raw_version = top_match.group(2)
  309.                     current_block.distributions = top_match.group(3).lstrip()
  310.  
  311.                     pairs = line.split(";", 1)[1]
  312.                     all_keys = {}
  313.                     other_pairs = {}
  314.                     for pair in pairs.split(','):
  315.                         pair = pair.strip()
  316.                         kv_match = keyvalue.match(pair)
  317.                         if kv_match is None:
  318.                             self._parse_error("Invalid key-value "
  319.                                         "pair after ';': %s" % pair, strict)
  320.                             continue
  321.                         key = kv_match.group(1)
  322.                         value = kv_match.group(2)
  323.                         if key.lower() in all_keys:
  324.                             self._parse_error("Repeated key-value: "
  325.                                     "%s" % key.lower(), strict)
  326.                         all_keys[key.lower()] = value
  327.                         if key.lower() == "urgency":
  328.                             val_match = value_re.match(value)
  329.                             if val_match is None:
  330.                                 self._parse_error("Badly formatted "
  331.                                         "urgency value: %s" % value, strict)
  332.                             else:
  333.                                 current_block.urgency = val_match.group(1)
  334.                                 comment = val_match.group(2)
  335.                                 if comment is not None:
  336.                                     current_block.urgency_comment = comment
  337.                         else:
  338.                             other_pairs[key] = value
  339.                     current_block.other_pairs = other_pairs
  340.                     state = start_of_change_data
  341.                 elif blank_match is not None:
  342.                     if state == first_heading:
  343.                         self.initial_blank_lines.append(line)
  344.                     else:
  345.                         self._blocks[-1].add_trailing_line(line)
  346.                 else:
  347.                     emacs_match = emacs_variables.match(line)
  348.                     vim_match = vim_variables.match(line)
  349.                     cvs_match = cvs_keyword.match(line)
  350.                     comments_match = comments.match(line)
  351.                     more_comments_match = more_comments.match(line)
  352.                     if ((emacs_match is not None or vim_match is not None)
  353.                             and state != first_heading):
  354.                         self._blocks[-1].add_trailing_line(line)
  355.                         old_state = state
  356.                         state = slurp_to_end
  357.                         continue
  358.                     if (cvs_match is not None or comments_match is not None
  359.                             or more_comments_match is not None):
  360.                         if state == first_heading:
  361.                             self.initial_blank_lines.append(line)
  362.                         else:
  363.                             self._blocks[-1].add_trailing_line(line)
  364.                         continue
  365.                     if ((old_format_re1.match(line) is not None
  366.                         or old_format_re2.match(line) is not None
  367.                         or old_format_re3.match(line) is not None
  368.                         or old_format_re4.match(line) is not None
  369.                         or old_format_re5.match(line) is not None
  370.                         or old_format_re6.match(line) is not None
  371.                         or old_format_re7.match(line) is not None
  372.                         or old_format_re8.match(line) is not None)
  373.                         and state != first_heading):
  374.                             self._blocks[-1].add_trailing_line(line)
  375.                             old_state = state
  376.                             state = slurp_to_end
  377.                             continue
  378.                     self._parse_error("Unexpected line while looking "
  379.                             "for %s: %s" % (state, line), strict)
  380.                     if state == first_heading:
  381.                         self.initial_blank_lines.append(line)
  382.                     else:
  383.                         self._blocks[-1].add_trailing_line(line)
  384.             elif (state == start_of_change_data
  385.                     or state == more_changes_or_trailer):
  386.                 change_match = change.match(line)
  387.                 end_match = endline.match(line)
  388.                 end_no_details_match = endline_nodetails.match(line)
  389.                 blank_match = blankline.match(line)
  390.                 if change_match is not None:
  391.                     changes.append(line)
  392.                     state = more_changes_or_trailer
  393.                 elif end_match is not None:
  394.                     if end_match.group(3) != '  ':
  395.                         self._parse_error("Badly formatted trailer "
  396.                                 "line: %s" % line, strict)
  397.                         current_block._trailer_separator = end_match.group(3)
  398.                     current_block.author = "%s <%s>" \
  399.                         % (end_match.group(1), end_match.group(2))
  400.                     current_block.date = end_match.group(4)
  401.                     current_block._changes = changes
  402.                     self._blocks.append(current_block)
  403.                     changes = []
  404.                     current_block = ChangeBlock()
  405.                     state = next_heading_or_eof
  406.                 elif end_no_details_match is not None:
  407.                     if not allow_empty_author:
  408.                         self._parse_error("Badly formatted trailer "
  409.                                 "line: %s" % line, strict)
  410.                         continue
  411.                     current_block._changes = changes
  412.                     self._blocks.append(current_block)
  413.                     changes = []
  414.                     current_block = ChangeBlock()
  415.                     state = next_heading_or_eof
  416.                 elif blank_match is not None:
  417.                     changes.append(line)
  418.                 else:
  419.                     cvs_match = cvs_keyword.match(line)
  420.                     comments_match = comments.match(line)
  421.                     more_comments_match = more_comments.match(line)
  422.                     if (cvs_match is not None or comments_match is not None
  423.                             or more_comments_match is not None):
  424.                         changes.append(line)
  425.                         continue
  426.                     self._parse_error("Unexpected line while looking "
  427.                             "for %s: %s" % (state, line), strict)
  428.                     changes.append(line)
  429.             elif state == slurp_to_end:
  430.                 if old_state == next_heading_or_eof:
  431.                     self._blocks[-1].add_trailing_line(line)
  432.                 else:
  433.                     changes.append(line)
  434.             else:
  435.                  assert False, "Unknown state: %s" % state
  436.                 
  437.         if ((state != next_heading_or_eof and state != slurp_to_end)
  438.             or (state == slurp_to_end and old_state != next_heading_or_eof)):
  439.             self._parse_error("Found eof where expected %s" % state,
  440.                     strict)
  441.             current_block._changes = changes
  442.             current_block._no_trailer = True
  443.             self._blocks.append(current_block)
  444.  
  445.     def get_version(self):
  446.         """Return a Version object for the last version"""
  447.         return self._blocks[0].version
  448.  
  449.     def set_version(self, version):
  450.         """Set the version of the last changelog block
  451.  
  452.         version can be a full version string, or a Version object
  453.         """
  454.         self._blocks[0].version = Version(version)
  455.  
  456.     version = property(get_version, set_version,
  457.                  doc="Version object for last changelog block""")
  458.  
  459.     ### For convenience, let's expose some of the version properties
  460.     full_version = property(lambda self: self.version.full_version)
  461.     epoch = property(lambda self: self.version.epoch)
  462.     debian_version = property(lambda self: self.version.debian_version)
  463.     upstream_version = property(lambda self: self.version.upstream_version)
  464.  
  465.     def get_package(self):
  466.         """Returns the name of the package in the last version."""
  467.         return self._blocks[0].package
  468.   
  469.     def set_package(self, package):
  470.         self._blocks[0].package = package
  471.  
  472.     package = property(get_package, set_package,
  473.                      doc="Name of the package in the last version")
  474.  
  475.     def get_versions(self):
  476.         """Returns a list of version objects that the package went through."""
  477.         return [block.version for block in self._blocks]
  478.  
  479.     versions = property(get_versions,
  480.                       doc="List of version objects the package went through")
  481.  
  482.     def _raw_versions(self):
  483.         return [block._raw_version for block in self._blocks]
  484.  
  485.     def __str__(self):
  486.         cl = "\n".join(self.initial_blank_lines)
  487.         for block in self._blocks:
  488.             cl += str(block)
  489.         return cl
  490.  
  491.     def set_distributions(self, distributions):
  492.         self._blocks[0].distributions = distributions
  493.     distributions = property(lambda self: self._blocks[0].distributions,
  494.                            set_distributions)
  495.  
  496.     def set_urgency(self, urgency):
  497.         self._blocks[0].urgency = urgency
  498.     urgency = property(lambda self: self._blocks[0].urgency, set_urgency)
  499.  
  500.     def add_change(self, change):
  501.         self._blocks[0].add_change(change)
  502.  
  503.     def set_author(self, author):
  504.         self._blocks[0].author = author
  505.     author = property(lambda self: self._blocks[0].author, set_author)
  506.  
  507.     def set_date(self, date):
  508.         self._blocks[0].date = date
  509.     date = property(lambda self: self._blocks[0].date, set_date)
  510.  
  511.     def new_block(self, **kwargs):
  512.         block = ChangeBlock(**kwargs)
  513.         block.add_trailing_line('')
  514.         self._blocks.insert(0, block)
  515.  
  516.     def write_to_open_file(self, file):
  517.         file.write(self.__str__())
  518.  
  519. def _test():
  520.     import doctest
  521.     doctest.testmod()
  522.  
  523.     unittest.main()
  524.  
  525. class ChangelogTests(unittest.TestCase):
  526.  
  527.     def test_create_changelog(self):
  528.         c = open('test_changelog').read()
  529.         cl = Changelog(c)
  530.         cs = str(cl)
  531.         clines = c.split('\n')
  532.         cslines = cs.split('\n')
  533.         for i in range(len(clines)):
  534.             self.assertEqual(clines[i], cslines[i])
  535.         self.assertEqual(len(clines), len(cslines), "Different lengths")
  536.  
  537.     def test_create_changelog_single_block(self):
  538.         c = open('test_changelog').read()
  539.         cl = Changelog(c, max_blocks=1)
  540.         cs = str(cl)
  541.         self.assertEqual(cs,
  542.         """gnutls13 (1:1.4.1-1) unstable; urgency=HIGH
  543.  
  544.   [ James Westby ]
  545.   * New upstream release.
  546.   * Remove the following patches as they are now included upstream:
  547.     - 10_certtoolmanpage.diff
  548.     - 15_fixcompilewarning.diff
  549.     - 30_man_hyphen_*.patch
  550.   * Link the API reference in /usr/share/gtk-doc/html as gnutls rather than
  551.     gnutls-api so that devhelp can find it.
  552.  
  553.  -- Andreas Metzler <ametzler@debian.org>  Sat, 15 Jul 2006 11:11:08 +0200
  554.  
  555. """)
  556.  
  557.     def test_modify_changelog(self):
  558.         c = open('test_modify_changelog1').read()
  559.         cl = Changelog(c)
  560.         cl.package = 'gnutls14'
  561.         cl.version = '1:1.4.1-2'
  562.         cl.distributions = 'experimental'
  563.         cl.urgency = 'medium'
  564.         cl.add_change('  * Add magic foo')
  565.         cl.author = 'James Westby <jw+debian@jameswestby.net>'
  566.         cl.date = 'Sat, 16 Jul 2008 11:11:08 -0200'
  567.         c = open('test_modify_changelog2').read()
  568.         clines = c.split('\n')
  569.         cslines = str(cl).split('\n')
  570.         for i in range(len(clines)):
  571.             self.assertEqual(clines[i], cslines[i])
  572.         self.assertEqual(len(clines), len(cslines), "Different lengths")
  573.  
  574.     def test_add_changelog_section(self):
  575.         c = open('test_modify_changelog2').read()
  576.         cl = Changelog(c)
  577.         cl.new_block(package='gnutls14',
  578.                 version=Version('1:1.4.1-3'),
  579.                 distributions='experimental',
  580.                 urgency='low',
  581.                 author='James Westby <jw+debian@jameswestby.net>')
  582.  
  583.         self.assertRaises(ChangelogCreateError, cl.__str__)
  584.  
  585.         cl.set_date('Sat, 16 Jul 2008 11:11:08 +0200')
  586.         cl.add_change('')
  587.         cl.add_change('  * Foo did not work, let us try bar')
  588.         cl.add_change('')
  589.  
  590.         c = open('test_modify_changelog3').read()
  591.         clines = c.split('\n')
  592.         cslines = str(cl).split('\n')
  593.         for i in range(len(clines)):
  594.             self.assertEqual(clines[i], cslines[i])
  595.         self.assertEqual(len(clines), len(cslines), "Different lengths")
  596.  
  597.     def test_strange_changelogs(self):
  598.         """ Just opens and parses a strange changelog """
  599.         c = open('test_strange_changelog').read()
  600.         cl = Changelog(c)
  601.  
  602.     def test_set_version_with_string(self):
  603.         c1 = Changelog(open('test_modify_changelog1').read())
  604.         c2 = Changelog(open('test_modify_changelog1').read())
  605.  
  606.         c1.version = '1:2.3.5-2'
  607.         c2.version = Version('1:2.3.5-2')
  608.         self.assertEqual(c1.version, c2.version)
  609.         self.assertEqual((c1.full_version, c1.epoch, c1.upstream_version,
  610.                           c1.debian_version),
  611.                          (c2.full_version, c2.epoch, c2.upstream_version,
  612.                           c2.debian_version))
  613.  
  614.     def test_changelog_no_author(self):
  615.         cl_no_author = """gnutls13 (1:1.4.1-1) unstable; urgency=low
  616.  
  617.   * New upstream release.
  618.  
  619.  --
  620. """
  621.         c1 = Changelog()
  622.         c1.parse_changelog(cl_no_author, allow_empty_author=True)
  623.         self.assertEqual(c1.author, None)
  624.         self.assertEqual(c1.date, None)
  625.         self.assertEqual(c1.package, "gnutls13")
  626.         c2 = Changelog()
  627.         self.assertRaises(ChangelogParseError, c2.parse_changelog, cl_no_author)
  628.  
  629.     def test_magic_version_properties(self):
  630.         c = Changelog(open('test_changelog'))
  631.         self.assertEqual(c.debian_version, '1')
  632.         self.assertEqual(c.full_version, '1:1.4.1-1')
  633.         self.assertEqual(c.upstream_version, '1.4.1')
  634.         self.assertEqual(c.epoch, '1')
  635.         self.assertEqual(str(c.version), c.full_version)
  636.  
  637.     def test_allow_full_stops_in_distribution(self):
  638.         c = Changelog(open('test_changelog_full_stops'))
  639.         self.assertEqual(c.debian_version, None)
  640.         self.assertEqual(c.full_version, '1.2.3')
  641.         self.assertEqual(str(c.version), c.full_version)
  642.  
  643. class VersionTests(unittest.TestCase):
  644.  
  645.     def _test_version(self, full_version, epoch, upstream, debian):
  646.         v = Version(full_version)
  647.         self.assertEqual(v.full_version, full_version, "Full version broken")
  648.         self.assertEqual(v.epoch, epoch, "Epoch broken")
  649.         self.assertEqual(v.upstream_version, upstream, "Upstram broken")
  650.         self.assertEqual(v.debian_version, debian, "Debian broken")
  651.  
  652.     def testversions(self):
  653.         self._test_version('1:1.4.1-1', '1', '1.4.1', '1')
  654.         self._test_version('7.1.ds-1', None, '7.1.ds', '1')
  655.         self._test_version('10.11.1.3-2', None, '10.11.1.3', '2')
  656.         self._test_version('4.0.1.3.dfsg.1-2', None, '4.0.1.3.dfsg.1', '2')
  657.         self._test_version('0.4.23debian1', None, '0.4.23debian1', None)
  658.         self._test_version('1.2.10+cvs20060429-1', None,
  659.                 '1.2.10+cvs20060429', '1')
  660.         self._test_version('0.2.0-1+b1', None, '0.2.0', '1+b1')
  661.         self._test_version('4.3.90.1svn-r21976-1', None,
  662.                 '4.3.90.1svn-r21976', '1')
  663.         self._test_version('1.5+E-14', None, '1.5+E', '14')
  664.         self._test_version('20060611-0.0', None, '20060611', '0.0')
  665.         self._test_version('0.52.2-5.1', None, '0.52.2', '5.1')
  666.         self._test_version('7.0-035+1', None, '7.0', '035+1')
  667.         self._test_version('1.1.0+cvs20060620-1+2.6.15-8', None,
  668.             '1.1.0+cvs20060620-1+2.6.15', '8')
  669.         self._test_version('1.1.0+cvs20060620-1+1.0', None,
  670.                 '1.1.0+cvs20060620', '1+1.0')
  671.         self._test_version('4.2.0a+stable-2sarge1', None, '4.2.0a+stable',
  672.                            '2sarge1')
  673.         self._test_version('1.8RC4b', None, '1.8RC4b', None)
  674.         self._test_version('0.9~rc1-1', None, '0.9~rc1', '1')
  675.         self._test_version('2:1.0.4+svn26-1ubuntu1', '2', '1.0.4+svn26',
  676.                            '1ubuntu1')
  677.         self._test_version('2:1.0.4~rc2-1', '2', '1.0.4~rc2', '1')
  678.  
  679.     def test_version_updating(self):
  680.         v = Version('1:1.4.1-1')
  681.  
  682.         v.debian_version = '2'
  683.         self.assertEqual(v.debian_version, '2')
  684.         self.assertEqual(v.full_version, '1:1.4.1-2')
  685.  
  686.         v.upstream_version = '1.4.2'
  687.         self.assertEqual(v.upstream_version, '1.4.2')
  688.         self.assertEqual(v.full_version, '1:1.4.2-2')
  689.  
  690.         v.epoch = '2'
  691.         self.assertEqual(v.epoch, '2')
  692.         self.assertEqual(v.full_version, '2:1.4.2-2')
  693.  
  694.         self.assertEqual(str(v), v.full_version)
  695.  
  696.         v.full_version = '1:1.4.1-1'
  697.         self.assertEqual(v.full_version, '1:1.4.1-1')
  698.         self.assertEqual(v.epoch, '1')
  699.         self.assertEqual(v.upstream_version, '1.4.1')
  700.         self.assertEqual(v.debian_version, '1')
  701.  
  702. if __name__ == "__main__":
  703.     _test()
  704.  
  705.